package com.monochromebytes.mononet.stub;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;

import com.monochromebytes.mononet.virtual.Connection;
import com.monochromebytes.mononet.virtual.ConnectionSocket;
import com.monochromebytes.mononet.virtual.ConnectionSocketListener;
import com.monochromebytes.mononet.virtual.VirtualAddress;

public class StubSocket implements ConnectionSocketListener {

    private final ConnectionSocket              socket;
    private final StubArbiter                   arbiter;
    private final HashMap<VirtualAddress, Stub> stubs;
    private final ArrayList<StubSocketListener> listeners;

    public StubSocket(InetSocketAddress localAddress, StubArbiter arbiter) {
        this.stubs = new HashMap<VirtualAddress, Stub>();
        this.listeners = new ArrayList<StubSocketListener>();
        this.socket = new ConnectionSocket(localAddress, arbiter);
        this.socket.addSocketListener(this);
        this.arbiter = arbiter;
    }

    public void open() throws IOException {
        this.socket.open();
    }

    public void close() throws IOException {
        this.socket.close();
    }

    public Stub getStub(VirtualAddress address) {
        synchronized (this.stubs) {
            // Check for existing Stub.
            Stub stub = this.stubs.get(address);

            if (stub == null) {
                // Fetch the serializer.
                Serializer serializer = this.arbiter.getSerializer(address);

                // Fetch Connection.
                Connection connection = this.socket.getConnection(address);

                // Create Stub.
                stub = new Stub(connection, serializer, this);
            }

            return stub;
        }
    }

    @Override
    public void connected(ConnectionSocket socket, Connection connection) {
        VirtualAddress address = connection.getAddress();
        Stub stub = this.getStub(address);

        // Notify listeners.
        synchronized (this.listeners) {
            for (StubSocketListener listener : this.listeners) {
                listener.incomingStub(stub);
            }
        }
    }

    @Override
    public void socketClosed(ConnectionSocket socket, IOException e) {
        // Notify listeners.
        synchronized (this.listeners) {
            for (StubSocketListener listener : this.listeners) {
                listener.socketClosed(this, e);
            }
        }
    }

    @Override
    public void socketOpened(ConnectionSocket socket) {
        // Notify listeners.
        synchronized (this.listeners) {
            for (StubSocketListener listener : this.listeners) {
                listener.socketOpened(this);
            }
        }
    }

    public void addStubListener(StubSocketListener listener) {
        synchronized (this.listeners) {
            this.listeners.add(listener);
        }
    }

    public void removeStubListener(StubSocketListener listener) {
        synchronized (this.listeners) {
            this.listeners.remove(listener);
        }
    }

    public void removeStub(Stub stub) {
        synchronized (this.stubs) {
            this.stubs.remove(stub.getAddress());
        }
    }
}
